//
// process_UNIX.cpp
// libprotea
// Tools Library for the Protea Project / Process/Thread functionality, specific to UNIX
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../OS_defines.h"
#ifdef OS_UNIX

#include "../defines.h"
#include "../AppHandler/AppInterface.h"
#include "process.h"
#include "pipes.h"

long XCGetThreadIdentifier() {
	return (long) pthread_self();
};

pid_t XCGetProcessIdentifier() {
	return getpid();
};

create_process_return XCCreateProcess( const char* file, int pcount, char** parms, PORT_HANDLE handle ) {
	create_process_return result;
	memset( &result, 0, sizeof( create_process_return ) );

	INFUNC( XCCreateProcess, NULL )
	// Am I the forked process or the parent?
	int pid = fork();
	if (pid == 0) { // If we are the child...
		char** exec_parms;
		char pipenum[ 25 ];

		// Create execution parameters
		exec_parms = new char*[pcount+3];

		// First parameter is always the file name
		exec_parms[0] = (char*)file;

		// Copy existing parameters
		int count;
		for (count = 0; count < pcount; count++)
			exec_parms[count+1] = parms[count];
		count++;

		// If using pipes, then we need to append the pipe number
		if (handle) {
			sprintf( pipenum, "%ld", handle );
			exec_parms[count++] = pipenum;
		};

		// Always NULL terminate
		exec_parms[count++] = NULL;

		// We will now start execution of the real process
		// the pipes/sockets are still open files at this point
		// and should be read-able by the child process

		// If we have parameters, then start this way
/*
		printf( "execv( %s", file );
		char** tempparms = exec_parms;
		while (*tempparms) {
			printf( ", %s", *tempparms );
			tempparms++;
		};
		printf( " )\n" );
*/
		if (execv( file, exec_parms )==-1) {
			delete [] exec_parms;
			RAISEOSERROR_PARAM( file );
		};

		// DO WHERE EVER GET HERE????  Cleanup
		delete [] exec_parms;
	} else if (pid == -1) {
		// Fork failed, output error message
		RAISEOSERROR();
	} else {
		result.handle = pid;
		result.id = pid;
	};

	OUTFUNC();

	return result;
};


process_info* XCMakeChild( XCAppInterface* app, char* file, process_info_ptr& plist, long pcount, char** parms, bool usepipe, app_start_message* app_start ) 
{
	INFUNC( XCMakeChild, app )

	// Need to handle errors better!!!

	process_info *proc = XCAddProcess( plist );

	XCPipeTransport* pPipe = NULL;
	XCSocketTransport* pSocket = NULL;
	PORT_HANDLE handle = 0;

	if ( usepipe ) 
	{
		proc->server = pPipe = new XCPipeTransport( app );
		proc->client = pPipe->Create();

		handle = proc->client->GetInHandle();
	}
	else
	{
		proc->server = NULL;
		proc->client = NULL;
	};

	proc->file = strdup( file );
	if (app_start)
		memcpy( &proc->startup, app_start, sizeof( app_start_message ) );
	else
		memset( &proc->startup, 0, sizeof( app_start_message ) );
	proc->active = false;

	// Create the process
	create_process_return result = XCCreateProcess( file, pcount, parms, handle );
	proc->pid = result.id;

	// At this point, we have a client, attempting to connect and read init info
	if ( usepipe )
	{
		init_message init;

		memset( &init, 0, sizeof( init_message ) );
		
		init.major = 1;
		init.minor = 0;
		init.response = proc->client->GetOutHandle();
		init.close_me[0] = proc->server->GetInHandle();
		init.close_me[1] = proc->server->GetOutHandle();
		init.parentpid = XCGetProcessIdentifier();

		// Fill in application information
		if (app_start)
			memcpy( &init.startup_parms, app_start, sizeof( app_start_message ) );
		else
			memset( &init.startup_parms, 0, sizeof( app_start_message ) );

		// Parent process
		proc->client->Close();

		// Send our information
		proc->server->Send( &init, sizeof( init_message ) );

	};
	app->ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Process %s created, transport = %ld", file, proc->server );

	OUTFUNCRET( proc )
};

// Unixes need to wait for all child process to end, otherwards we get zombies
void XCWaitChildren( XCAppInterface* app, int signum ) {
	pid_t pid;

	while ((pid = waitpid( -1, NULL, WNOHANG)) > 0) {
		app->DroppedProcess( pid );
	};
};

void XCWaitForChildrenToExit() {
	int result;

	do {
		result = wait( NULL );
	} while (result > 0);
};

int XCExecuteAndWait( char* file, int pcount, char** parms ) {
	INFUNC( XCExecuteAndWait, NULL )

	create_process_return result = XCCreateProcess( file, pcount, parms, 0 );
	PORT_HANDLE handle = result.handle;

	int status;

	if (waitpid( handle, &status, 0 ) == -1)
		RAISEOSERROR();

	if (WIFEXITED( status )) {
		RETURNFUNC( WEXITSTATUS( status ) );
	} else {
		RETURNFUNC( -1 );
	};

	return 0;
	OUTFUNC();
};

THREAD_HANDLE XCCreateThread( XCThreadProc proc, void* parm ) {
	THREAD_HANDLE id;
	pthread_create( &id, NULL, proc, parm );
	return id;
};

void XCJoinThread( THREAD_HANDLE thread ) {
	pthread_join( thread, NULL );
};

///
//
// Critical section code
//
XCCriticalSection::XCCriticalSection() {
	pthread_mutex_init( &mutex, NULL );	
};

XCCriticalSection::~XCCriticalSection() {
	pthread_mutex_destroy( &mutex );
};

void XCCriticalSection::Lock() {
	pthread_mutex_lock( &mutex );
};

void XCCriticalSection::UnLock() {
	pthread_mutex_unlock( &mutex );
};

bool XCCriticalSection::TryLock() {
	return pthread_mutex_trylock( &mutex );
};

XCEvent::XCEvent( bool state ) {
	pthread_mutex_init( &mutex, NULL );
	pthread_cond_init( &event, NULL );
	waiting = false;
};

XCEvent::~XCEvent() {
	pthread_cond_destroy( &event );
	pthread_mutex_destroy( &mutex );
};

void XCEvent::Wait() {
	pthread_mutex_lock( &mutex );
	waiting = true;
//	cout << "Condition Wait" << endl;
	pthread_cond_wait( &event, &mutex );
	waiting = false;
    pthread_mutex_unlock( &mutex );
};

void XCEvent::Raise() {
	// Spin while waiting is false
	while (!waiting)
		usleep( 100 );

	// Signal the condition
//	cout << "Condition Raise" << endl;
	pthread_cond_signal( &event );
};

XCManualEvent::XCManualEvent( bool state ) {
	waiting = state;
};

void XCManualEvent::Reset() {
	waiting = false;
};

void XCManualEvent::Raise() {
	waiting = true;
};

XC_WAIT_RESULT XCManualEvent::Wait( int timeout ) {
	int counter = timeout*10;
	while (!waiting && counter > 0) {
		usleep( 1E5 );
		counter--;
	};
	if (waiting)
		return XCWAIT_OBJECT;
	else
		return XCWAIT_TIMEOUT;
};

bool XCManualEvent::GetState() {
	return waiting;
};

////////
//
// Thread local storage class
//
XCTLS::XCTLS() {
	pthread_key_create( &index, NULL );
};

XCTLS::~XCTLS() {
	pthread_key_delete( index );
};

const void* XCTLS::GetData() {
	return pthread_getspecific( index );
};

void XCTLS::SetData( const void* data ) {
	pthread_setspecific( index, data );
};

#endif // OS_UNIX
